﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Returns generator for Dictionary to Dictionary mappings.
    /// </summary>
    public static partial class Generators
    {
        public static MappingExpressionGenerator SpecializedCollectionToSpecializedCollection()
        {
            return (fromExpr, to, customMapping, referenceDictionary) =>
                   (IsEnumerable(fromExpr.Type) && IsSpecializedCollection(to))
                       ? customMapping ?? GenerateSpecializedCollectionExpression(fromExpr, to, referenceDictionary)
                       : null;
        }

        private static Expression GenerateSpecializedCollectionExpression(Expression fromExpr,
                                                                Type to, ParameterExpression referenceDictionary)
        {
            if (IsBitVector32(to))
                return GenerateBitVector32Expression(fromExpr, to, referenceDictionary);
            if (IsStringCollection(to))
                return GenerateStringCollectionExpression(fromExpr, to, referenceDictionary);

            throw new InvalidOperationException("Cannot find relevant ExpressionGenerator");
        }

        private static Expression GenerateBitVector32Expression( Expression fromExpr,
                                                               Type to, ParameterExpression referenceDictionary)
        {
            Debug.WriteLine("\tCollectionToBitVector32 ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                                 to.Name);

            Type fromElementType = GetCollectionElementType(fromExpr.Type);

            MethodInfo convertAllMethod = typeof(Generators)
                .GetMethod("ConvertBitVector32", BindingFlags.Static | BindingFlags.Public)
                .MakeGenericMethod(fromElementType);

            return Expression.Call(convertAllMethod, fromExpr, referenceDictionary);
        }

        private static Expression GenerateStringCollectionExpression(Expression fromExpr,
                                                              Type to, ParameterExpression referenceDictionary)
        {
            Debug.WriteLine("\tCollectionToStringCollection ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                                 to.Name);

            Type fromElementType = GetCollectionElementType(fromExpr.Type);
            
            MethodInfo convertAllMethod = typeof(Generators)
                .GetMethod("ConvertStringCollection", BindingFlags.Static | BindingFlags.Public)
                .MakeGenericMethod(fromElementType);

            return Expression.Call(convertAllMethod, fromExpr, referenceDictionary);
        }

        public static BitVector32 ConvertBitVector32<TInput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return new BitVector32();

            var iterator = collection.GetEnumerator();
            iterator.MoveNext();

            BitVector32 result = new BitVector32();
            for (int i = 0; i < collection.Count(); i++)
            {
                result[i] = Mapper.Map<TInput, bool>(iterator.Current, referenceDictionary);
                iterator.MoveNext();
            }
            return result;
        }

        public static StringCollection ConvertStringCollection<TInput>(IEnumerable<TInput> collection, ReferenceDictionary referenceDictionary)
        {
            if (collection == null)
                return null;

            StringCollection result = new StringCollection();
            foreach (var input in collection)
            {
                result.Add(Mapper.Map<TInput, string>(input, referenceDictionary));
            }
            return result;
        }

        private static bool IsSpecializedCollection(Type t)
        {
            if (IsBitVector32(t) ||
                IsStringCollection(t))
                return true;

            return false;
        }

        private static bool IsStringCollection(Type t)
        {
            return t == typeof(StringCollection);
        }

        private static bool IsBitVector32(Type t)
        {
            return t == typeof(BitVector32);
        }
    }
}
